//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

//  The code in this file was heavily inspired by Marcin Kalicinski's RapidXML

#ifndef _BUOLA_XML_CDOC_H_
#define _BUOLA_XML_CDOC_H_

#include <buola/memory/umemorypool.h>
#include <buola/xml/exceptions.h>
#include <buola/xml/enums.h>
#include <buola/container/intrusive/slist.h>
#include <buola/container/intrusive/list.h>

namespace buola { namespace xml {

///\addtogroup xml
///@{

///////////////////////////////////////////////////////////////////////////
// XML document

//! This class represents root of the DOM hierarchy. 
//! It is also an xml_node and a memory_pool through public inheritance.
//! Use parse() function to build a DOM tree from a zero-terminated XML text string.
//! parse() function allocates memory for nodes and attributes by using functions of xml_document, 
//! which are inherited from memory_pool.
//! To access root node of the document, use the document itself, as if it was an xml_node.
//! \param Ch Character type to use.
class CDoc
{
    friend CNode;
    typedef std::string::iterator TIterator;
    
    struct SAttribute : public intrusive::slist_hook, public UUncopiable
    {
        SAttribute(const string_view &pName,const string_view &pValue) 
            :   mName(pName)
            ,   mValue(pValue)
        {}

        string_view mName;
        string_view mValue;
    };

    struct SNode : public intrusive::list_hook, public UUncopiable
    {
        SNode(CDoc *pDoc,ENodeType pType,const string_view &pName=string_view(),const string_view &pValue=string_view())
            :   mDoc(pDoc)
            ,   mType(pType)
            ,   mName(pName)
            ,   mValue(pValue)
        {}

        CDoc *mDoc;
        ENodeType mType;
        string_view mName;
        string_view mValue;
        intrusive::list<SNode> mChildren;
        intrusive::slist<SAttribute> mAttributes;
    };
    
public:
    //! Constructs empty XML document
    CDoc()
        :   mParseFlags(EParseFlags::DEFAULT)
    {}

    ~CDoc();
    
    CNode Root();
    CNode DocNode();
    CNode NewNode(const std::string &pName,const std::string &pValue=std::string());
    
    ///load from a URL
    bool Load(const io::CURI&);
    ///save to a URL
    bool Save(const io::CURI&);

    void Parse(const std::string &pText);
    void Parse(std::string &&pText);
    void Parse(const char *pText,size_t pLength);
    void Parse(const char *pText);

    void Create(const std::string &pRoot);

    void Clear();
    
    void AddDocType(const std::string &pName,const std::string &pExternal,const std::string &pSystem);
    
    std::string ToString() const;
    
private:
    //node management
    string_view CreateString(const std::string &pString);
    SNode *CreateNode(ENodeType pType,const std::string &pName=std::string(),const std::string &pValue=std::string());
    SAttribute *CreateAttribute(const std::string &pName,const std::string &pValue);

    ///////////////////////////////////////////////////////////////////////
    // Internal character utility functions
    
    // Detect whitespace character
    static unsigned char Test(char pChar,const unsigned char *pTable);
    // Insert coded character, using UTF8 or 8-bit ASCII
    void InsertCodedCharacter(TIterator &pI,unsigned long pCode);

    static void Skip(TIterator &pI,const unsigned char *pTable);
    static void SkipUntil(TIterator &pI,char p1);
    static void SkipUntil(TIterator &pI,char p1,char p2);
    static void SkipUntil(TIterator &pI,char p1,char p2,char p3);
    static void Expect(TIterator &pI,const unsigned char *pTable);
    static void Expect(TIterator &pI,char pChar);
    static void Expect2(TIterator &pI,char p1,char p2);
    static char ExpectOneOf(TIterator &pI,char p1,char p2);

    // Skip characters until predicate evaluates to true while doing the following:
    // - replacing XML character entity references with proper characters (&apos; &amp; &quot; &lt; &gt; &#...;)
    // - condensing whitespace sequences to single space character
    TIterator SkipAndExpand(TIterator &pI,const unsigned char *pTable,const unsigned char *pTablePure);

    ///////////////////////////////////////////////////////////////////////
    // Internal parsing functions
    
    // Parse BOM, if any
    void ParseBOM(TIterator &pI);
    // Parse XML declaration (<?xml...)
    SNode *ParseXMLDeclaration(TIterator &pI);
    // Parse XML comment (<!--...)
    SNode *ParseComment(TIterator &pI);
    // Parse DOCTYPE
    SNode *ParseDocType(TIterator &pI);
    // Parse PI
    SNode *ParsePI(TIterator &pI);
    // Parse and append data
    void ParseAndAppendData(TIterator &pI,SNode *pNode,TIterator pStart);
    // Parse CDATA
    SNode *ParseCDATA(TIterator &pI);
    // Parse element node
    SNode *ParseElement(TIterator &pI);
    // Determine node type, and parse it
    // < is already skipped here
    SNode *ParseNode(TIterator &pI);
    // Parse contents of the node - children, data etc.
    void ParseNodeContents(TIterator &pI,SNode *pNode);
    // Parse XML attributes of the node
    void ParseNodeAttributes(TIterator &pI,SNode *pNode);

    void DoParse();
    
private:
    std::string mText;
    SNode *mDocNode;
    UMemoryPool<> mPool;
    EParseFlags mParseFlags;
};

///@}

/*namespace xml*/ } /*namespace buola*/ }

#endif
